package cn.backflow.lib.util;


import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class StringUtil {
    private static final String DEFAULT_CHARSET = "utf-8";
    private static final String ALGORITHM_MD5 = "MD5";
    private static final String ALGORITHM_SHA1 = "SHA1";
    private static final String ALGORITHM_HMACSHA1 = "HmacSHA1";
    // 用于生成短链接的62进制字符数组
    private static final char[] DIGITS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
            'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    };
    private static final char[] HEX = Arrays.copyOfRange(DIGITS, 0, 16);
    //定义script的正则表达式：<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>
    private final static Pattern htmlScriptPattern = Pattern.compile("<script[^>]*?>[\\s\\S]*?<\\/script>", Pattern.CASE_INSENSITIVE);
    //定义style的正则表达式: <[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>
    private final static Pattern htmlStylePattern = Pattern.compile("<style[^>]*?>[\\s\\S]*?<\\/style>", Pattern.CASE_INSENSITIVE);
    //定义HTML标签的正则表达式
    private final static Pattern htmlTagPattern = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE);
    //定义HTML中的转义字符,一些特殊字符处理，主要是&开头;结尾
    private final static Pattern htmlSpecialCharPattern = Pattern.compile("&[a-z]+;", Pattern.CASE_INSENSITIVE);
    private static Random rnd = new Random();
    private static Logger log = LoggerFactory.getLogger(StringUtil.class);

    /**
     * 测试主函数
     */
    static public void main(String[] args) throws Exception {
        int a = 0;
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            list.add(reverse_shorten() + " : " + ++a);

        }
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);

    }

    /**
     * 转换当前系统时间为短链接
     */
    public static synchronized String shorten() {
        try {
            Thread.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return shorten(System.currentTimeMillis());
    }

    /**
     * 转换当前系统时间为短链接 (从大到小)
     */
    public static synchronized String reverse_shorten() {
        try {
            Thread.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return shorten(9999999999999L - System.currentTimeMillis());
    }

    /**
     * 自增序列方式生成短链接
     *
     * @param seq 自增序列值
     * @return 生成后的短链接
     */
    public static String shorten(long seq) {
        StringBuilder sb = new StringBuilder();
        int len = DIGITS.length;
        do {
            int remainder = (int) (seq % len);
            sb.append(DIGITS[remainder]);
            seq = seq / len;
        } while (seq != 0);
        return sb.reverse().toString();
    }

    public static String shorten(String longUrl) {
        String shortUrl = shorten(longUrl, 5);
        if (shortUrl.isEmpty()) {
            shortUrl = shorten(longUrl, 6);
        }
        return shortUrl;
    }

    /**
     * 基于MD5方式生成短链接
     *
     * @param longUrl 长链接
     * @param len     短链接长度
     * @return 生成的短链接
     */
    public static String shorten(String longUrl, int len) {
        if (len < 0 || len > 6) {
            throw new IllegalArgumentException("the length of url must be between 0 and 6");
        }
        String hex = DigestUtils.md5Hex(longUrl);
        // 6 digit binary can indicate 62 letter & number from 0-9a-zA-Z
        int binaryLen = len * 6;
        long binaryLenFixer = Long.valueOf(StringUtils.repeat("1", binaryLen), 0x2);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            String sub = StringUtils.substring(hex, i * 8, (i + 1) * 8);
            sub = Long.toBinaryString(Long.valueOf(sub, 16) & binaryLenFixer);
            sub = StringUtils.leftPad(sub, binaryLen, "0");
            for (int j = 0; j < len; j++) {
                String sub2 = StringUtils.substring(sub, j * 6, (j + 1) * 6);
                int charIndex = Integer.valueOf(sub2, 0x2) & 0x0000003d;
                sb.append(DIGITS[charIndex]);
            }
        }
        return sb.toString();
    }


    /**
     * 获取对象字符串值
     */
    public static String value(Object o) {
        return (o == null || isBlank(o.toString())) ? "" : o.toString();
    }

    /**
     * 判断一个字符串是否为null或是空字符串<p>
     *
     * @param cs The string for checking
     * @return true if the string is neither null nor empty string
     */
    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }


    /**
     * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p>
     * <p>
     * <pre>
     * StringUtils.isBlank(null)      = true
     * StringUtils.isBlank("")        = true
     * StringUtils.isBlank(" ")       = true
     * StringUtils.isBlank("bob")     = false
     * StringUtils.isBlank("  bob  ") = false
     * </pre>
     *
     * @param cs the CharSequence to check, may be null
     * @return {@code true} if the CharSequence is null, empty or whitespace
     * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence)
     */
    public static boolean isBlank(final CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static boolean isNotBlank(final CharSequence cs) {
        return !isBlank(cs);
    }

    /**
     * byte数组转化为16进制的String<p>
     *
     * @param bytes byte[] 字节数组
     * @return String
     * <p>把字节数组转换成可视字符串</p>
     */
    public static String toHex(byte[] bytes) {
        return toHex(bytes, 0, bytes.length);
    }

    /**
     * 将字符串data按照encode转化为byte数组，然后转化为16进制的String
     *
     * @param data   源字符串
     * @param encode 字符编码
     * @return 把字节数组转换成可视字符串
     */
    public static String toHex(String data, String encode) {
        try {
            return toHex(data.getBytes(encode));
        } catch (Exception e) {
            log.error("toHex:" + data + ",encode:" + encode);
        }
        return "";
    }

    /**
     * byte转化为16进制的String
     *
     * @param b the byte
     * @return 16进制的String
     */
    public static String toHex(byte b) {
        byte[] buf = {b};
        return toHex(buf);
    }

    /**
     * byte数组的部分字节转化为16进制的String
     *
     * @param bytes  待转换的byte数组
     * @param offset 开始位置
     * @param len    字节数
     * @return 16进制的String
     */
    public static String toHex(byte[] bytes, int offset, int len) {
        char buf[] = new char[len * 2];
        int k = 0;
        for (int i = offset; i < len; i++) {
            buf[k++] = HEX[((int) bytes[i] & 0xff) >> 4];
            buf[k++] = HEX[((int) bytes[i] & 0xff) % 16];
        }
        return new String(buf);
    }

    /**
     * 将16进制的字符串转换为byte数组，是toHex的逆运算
     *
     * @param hex 16进制的字符串
     * @return byte数组
     */
    public static byte[] hex2Bytes(String hex) {
        if (isEmpty(hex) || hex.length() % 2 > 0) {
            log.error("hex2Bytes: invalid HEX string:" + hex);
            return null;
        }
        int len = hex.length() / 2;
        byte[] ret = new byte[len];
        int k = 0;
        for (int i = 0; i < len; i++) {
            int c = hex.charAt(k++);
            if (c >= '0' && c <= '9')
                c = c - '0';
            else if (c >= 'a' && c <= 'f')
                c = c - 'a' + 10;
            else if (c >= 'A' && c <= 'F')
                c = c - 'A' + 10;
            else {
                log.error("hex2Bytes: invalid HEX string:" + hex);
                return null;
            }
            ret[i] = (byte) (c << 4);
            c = hex.charAt(k++);
            if (c >= '0' && c <= '9')
                c = c - '0';
            else if (c >= 'a' && c <= 'f')
                c = c - 'a' + 10;
            else if (c >= 'A' && c <= 'F')
                c = c - 'A' + 10;
            else {
                log.error("hex2Bytes: invalid HEX string:" + hex);
                return null;
            }
            ret[i] += (byte) c;
        }
        return ret;
    }

    public static String getRandomNumberStringBase36(int strLen) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < strLen; i++) {
            sb.append(DIGITS[rnd.nextInt(36)]);
        }
        return sb.toString();
    }

    /**
     * 获取一个随机数
     *
     * @param maxValue 可能的最大随机数
     * @return 不大于maxValue的整型数
     */
    public static int getRandomNumber(int maxValue) {
        return rnd.nextInt(maxValue);
    }

    /**
     * 将字符串按base64方式编码
     *
     * @param s 源字符串
     * @return 编码后的字符串
     */
    public static String base64Encode(String s) {
        if (s == null) return null;
        try {
            return (new sun.misc.BASE64Encoder()).encode(s.getBytes(DEFAULT_CHARSET));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * 将 BASE64 编码的字符串 s 进行解码
     *
     * @param s base64编码后的字符串
     * @return 解码后的字符串
     */
    public static String base64Decode(String s) {
        if (s == null)
            return null;
        try {
            return new String(base64DecodeBytes(s), DEFAULT_CHARSET);
        } catch (Exception e) {
            log.warn("base64Decode failed", e);
            return null;
        }
    }

    public static byte[] base64DecodeBytes(String s) {
        if (s == null)
            return null;
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        try {
            return decoder.decodeBuffer(s);
        } catch (Exception e) {
            log.warn("base64Decode failed", e);
            return null;
        }
    }

    /**
     * 计算字符串的md5的摘要信息
     *
     * @param s 源字符串
     * @return 32字节的16进制的字符串
     */
    public static String md5(String s) {
        return md5(s, null);
    }

    /**
     * 计算字符串的md5的摘要信息
     *
     * @param data 源字符串
     * @param key  salt字符串，
     * @return 32字节的16进制的字符串
     */
    public static String md5(String data, String key) {
        return digest(data, key, ALGORITHM_MD5);
    }

    /**
     * 计算字符串的SHA1的摘要信息
     *
     * @param data 源字符串
     * @param key  salt字符串，
     * @return 32字节的16进制的字符串
     */
    public static String sha1(String data, String key) {
        return digest(data, key, ALGORITHM_SHA1);
    }

    /**
     * 计算字符串的摘要信息
     *
     * @param data      源字符串
     * @param key       salt字符串，
     * @param algorithm 摘要算法名称，可以是MD5，SHA1等
     * @return 32字节的16进制的字符串
     */
    public static String digest(String data, String key, String algorithm) {
        String ret = "";
        if (isEmpty(data))
            return ret;

        try {
            byte[] keybytes = null;
            if (!isEmpty(key))
                keybytes = key.getBytes(DEFAULT_CHARSET);
            return digest(data.getBytes(DEFAULT_CHARSET), keybytes, algorithm);
        } catch (Exception e) {
            log.error("digest error:" + e);
        }
        return ret;
    }

    /**
     * 计算字符串的摘要信息
     *
     * @param data       源字节
     * @param key        salt，
     * @param digestName 摘要算法名称，可以是MD5，SHA1等
     * @return 32字节的16进制的字符串
     */
    public static String digest(byte[] data, byte[] key, String digestName) {
        byte[] bytes = digestBytes(data, key, digestName);
        if (bytes == null)
            return "";
        return toHex(bytes, 0, bytes.length);
    }

    public static byte[] digestBytes(byte[] data, byte[] key, String digestName) {
        if (data == null || data.length == 0)
            return null;

        try {
            MessageDigest mgd = MessageDigest.getInstance(digestName);
            mgd.update(data);
            byte[] bytes;
            if (key == null || key.length == 0) {
                bytes = mgd.digest();
            } else {
                bytes = mgd.digest(key);
            }
            mgd.reset();
            return bytes;
        } catch (Exception e) {
            log.error("digest error:" + e);
        }
        return null;
    }


    /**
     * hmacSHA1
     */
    public static String hmacSHA1(String data, String key) {
        String ret = "";
        if (isEmpty(data))
            return ret;

        try {
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(DEFAULT_CHARSET), ALGORITHM_HMACSHA1);
            Mac mac = Mac.getInstance(ALGORITHM_HMACSHA1);
            mac.init(signingKey);
            byte[] bytes = mac.doFinal(data.getBytes(DEFAULT_CHARSET));
            ret = toHex(bytes, 0, bytes.length);
            mac.reset();
        } catch (Exception ex) {
            log.error("hmacSHA1 error:", ex);
        }
        return ret;
    }

    public static boolean isNumeric(final CharSequence cs) {
        if (isEmpty(cs)) {
            return false;
        }
        final int sz = cs.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isDigit(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static boolean isEmail(String s) {
        return isNotBlank(s) && s.matches("^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$");
    }

    public static boolean isMobile(String s) {
        return !isEmpty(s) && s.matches("^(13|14|15|17|18)\\d{9}$");
    }

    public static boolean isPhoneNumber(String s) {
        return !isEmpty(s) && s.matches("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$");
    }

    public static boolean isDate(String s) {
        return !isEmpty(s) && s.matches("^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$");
    }

    public static boolean isNumber(String s) {
        return !isEmpty(s) && s.matches("^[-]*[0-9.]+$");
    }

    public static boolean isOnlyLetter(String s) {
        return !isEmpty(s) && s.matches("^[a-zA-Z ']+$");
    }

    public static boolean isImageFile(String s) {
        return !isEmpty(s) && s.matches("(.*)\\.(jpeg|jpg|bmp|gif|png)$");
    }

    public static boolean isOnlyChinese(String s) {
        return !isEmpty(s) && s.matches("[^u4e00-u9fa5]+$");
    }

    public static boolean isUrl(String s) {
        if (isEmpty(s)) return false;
        boolean ret = s.matches("^(https|http|ftp|rtsp|mms)?://[^\\s]*$");
        if (!ret)
            ret = s.matches("^[./?#a-zA-Z0-9-_=&;,%]*$");
        return ret;
    }

    /**
     * 可识别的windows GUID字符串转换为16位byte数组
     *
     * @param guid GUID字符串
     * @return 16位byte数组
     */
    public static byte[] guid2bytes(String guid) {
        //windows guid:75B22630-668E-11CF-A6D9-00AA0062CE6C ==> 3026B2758E66CF11A6D900AA0062CE6C
        StringBuilder sb = new StringBuilder(guid);
        sb.replace(0, 2, guid.substring(6, 8)).replace(2, 4, guid.substring(4, 6)).replace(4, 6, guid.substring(2, 4)).replace(6, 8, guid.substring(0, 2));
        sb.replace(9, 11, guid.substring(11, 13)).replace(11, 13, guid.substring(9, 11));
        sb.replace(14, 16, guid.substring(16, 18)).replace(16, 18, guid.substring(14, 16));
        return StringUtil.hex2Bytes(sb.toString().replace("-", ""));
    }

    /**
     * 转换16位byte数组为可识别的windows GUID字符串
     *
     * @param buf 16位byte数组
     * @return GUID字符串
     */
    public static String bytes2Guid(byte[] buf) {
        return bytes2Guid(buf, 0);
    }

    /**
     * 转换16位byte数组为可识别的windows GUID字符串
     *
     * @param buf    byte数组
     * @param offset 数组的开始位置
     * @return GUID字符串
     */
    public static String bytes2Guid(byte[] buf, int offset) {
        // 3026B2758E66CF11A6D900AA0062CE6C ==> 75B22630-668E-11CF-A6D9-00AA0062CE6C
        final int guidSize = 16;
        if (buf == null || offset < 0 || (offset + guidSize > buf.length))
            return "";

        String hex = StringUtil.toHex(buf, offset, guidSize);
        String sb = String.valueOf(hex.subSequence(6, 8)) + hex.subSequence(4, 6) + hex.subSequence(2, 4) + hex.subSequence(0, 2) +
                "-" + hex.subSequence(10, 12) + hex.subSequence(8, 10) +
                "-" + hex.subSequence(14, 16) + hex.subSequence(12, 14) +
                "-" + hex.subSequence(16, 20) +
                "-" + hex.substring(20);
        return sb.toUpperCase();
    }

    /**
     * 将ipv4的地址串转换成一个long型整数
     *
     * @param ip ipv4格式的地址串
     * @return long型整数
     */
    public static long valueOfIpv4(String ip) {
        long ret = 0;
        String[] p = ip.split("\\.");
        ret += Long.parseLong(p[0]) << 24;
        ret += Long.parseLong(p[1]) << 16;
        ret += Long.parseLong(p[2]) << 8;
        ret += Long.parseLong(p[3]);
        return ret;
    }

    /**
     * 将一个ipv4对应的long型整数转换成一个ipv4的地址串
     *
     * @param ip ipv4对应的long型整数
     * @return ipv4的地址串
     */
    public static String ipv4(long ip) {
        StringBuilder ret = new StringBuilder();
        ret.insert(0, ip % 256).insert(0, ".");
        ip >>= 8;
        ret.insert(0, ip % 256).insert(0, ".");
        ip >>= 8;
        ret.insert(0, ip % 256).insert(0, ".");
        ip >>= 8;
        ret.insert(0, ip);
        return ret.toString();
    }

    /*
     * 对特殊字符进行UNICODE转码,如\u0020
     */
    public static String convertSpecialChar2Unicode(String s) {
        if (null == s)
            return null;

        StringBuilder sb = new StringBuilder();
        char[] cs = s.toCharArray();

        for (char c : cs) {
            switch (c) {
                case '"':
                case '\'':
                case '\t':
                    sb.append("\\u00").append(Integer.toHexString(c).toUpperCase());
                    break;
                case '\n':
                case '\r':
                    sb.append("\\u000")
                            .append(Integer.toHexString(c).toUpperCase());
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 方法名称:transMapToString
     * 传入参数:map
     * 返回值:String 形如 username=chenziwen&password=1234
     */
    public static String transMapToString(Map map) {
        if (map == null) {
            return "";
        }
        java.util.Map.Entry entry;
        StringBuilder sb = new StringBuilder();
        for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext(); ) {
            entry = (java.util.Map.Entry) iterator.next();
            sb.append(entry.getKey().toString()).append("=").append(null == entry.getValue() ? "" :
                    entry.getValue().toString()).append(iterator.hasNext() ? "&" : "");
        }
        return sb.toString();
    }

    /**
     * 统计字符串字节数（中文和全角字符算2个字节长度,半角字符算1个字节长度）
     */
    public static int getByteLength4FontWidth(String str) {
        if (isEmpty(str))
            return 0;

        int n = 0;

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c < 256) {
                n++;
            } else if ((c >= 0xFF61) && (c <= 0xFF9F)) { // 半角的日语字符
                n++;
            } else if ((c >= 0xFFA0) && (c <= 0xFFBE)) { // 半角的韩语字符
                n++;
            } else if ((c >= 0xFFC2) && (c <= 0xFFC7)) { // 半角的韩语字符
                n++;
            } else if ((c >= 0xFFCA) && (c <= 0xFFCF)) { // 半角的韩语字符
                n++;
            } else if ((c >= 0xFFD2) && (c <= 0xFFD7)) { // 半角的韩语字符
                n++;
            } else if ((c >= 0xFFDA) && (c <= 0xFFDC)) { // 半角的韩语字符
                n++;
            } else if ((0xFFE8 <= c) && (c <= 0xFFEE)) { // 半角的特殊符号
                n++;
            } else {
                n += 2;
            }
        }
        return n;
    }

    public static String replaceTabAndLine(String content) {
        if (content != null) {
            char newLineChar = 0x0A;// 换行符ASC码
            content = content.replaceAll(newLineChar + "", " ");// 去掉换行符
            content = content.replaceAll("\n", " "); // 去掉换行符
            content = content.replaceAll("\t", " "); // 去掉 tab符
        }
        return content;
    }

    public static String filterOf4ByteStr(String text) {
        /**
         * 过滤掉超过3个字节的UTF8字符
         */
        if (StringUtil.isEmpty(text)) {
            return "";
        }
        String name = "";
        byte[] bytes;
        try {
            bytes = text.getBytes("utf-8");
            ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
            int i = 0;
            while (i < bytes.length) {
                short b = bytes[i];
                if (b > 0) {
                    buffer.put(bytes[i++]);
                    continue;
                }

                b += 256; // 去掉符号位

                if (((b >> 5) ^ 0x6) == 0) {
                    buffer.put(bytes, i, 2);
                    i += 2;
                } else if (((b >> 4) ^ 0xE) == 0) {
                    buffer.put(bytes, i, 3);
                    i += 3;
                } else if (((b >> 3) ^ 0x1E) == 0) {
                    i += 4;
                } else if (((b >> 2) ^ 0x3E) == 0) {
                    i += 5;
                } else if (((b >> 1) ^ 0x7E) == 0) {
                    i += 6;
                } else {
                    buffer.put(bytes[i++]);
                }
            }
            buffer.flip();
            name = new String(buffer.array(), "utf-8");
            return name;
        } catch (UnsupportedEncodingException e) {
            log.error("filterOf4ByteStr:" + text);
        }
        return name;
    }


    public static String replaceBlank(String str) {
        String dest = "";
        if (str != null) {
            Pattern p = Pattern.compile("[\t\r\n]");
            Matcher m = p.matcher(str);
            dest = m.replaceAll("");
        }
        return dest;
    }
}